﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using Weibo2Sfdc.SfWeb;


namespace Weibo2Sfdc
{
    public class Client
    {
        private String username;
        private String password;
        private string host;
        private int querySize;
        private int sessionlength;
        private String sessionId;
        private String serverUrl;
        private long nextLoginTime;
        private SforceService sfs;

        private String displayName;

        private bool useCompression;

        private QueryOptions queryOptions;
        private AssignmentRuleHeader assignmentRules = new AssignmentRuleHeader();

        /// <summary>
        /// Meta area
        /// </summary>
        private Weibo2Sfdc.SfMeta.MetadataService mts = new Weibo2Sfdc.SfMeta.MetadataService();
        private bool useMeta;
        public bool UseMeta
        {
            get
            {
                return useMeta;
            }
            set
            {
                useMeta = value;
            }
        }
        public Weibo2Sfdc.SfMeta.MetadataService MetadataService
        {
            get
            {
                return mts;
            }
            set
            {
                mts = value;
            }
        }

        public Client()
        {
            this.querySize = 500;
            this.sessionlength = 29;
            this.useCompression = true;
            this.host = @"https://login.salesforce.com/services/Soap/u/25.0";
        }
        #region Property

        public static class Util
        {
            private static Client instance;

            public static Client getInstance()
            {
                if (instance == null)
                {
                    instance = new Client();
                }
                return instance;
            }
        }

        public string Host { get { return host; } set { host = value; } }
        public SforceService binding
        {
            get
            {
                return sfs;
            }
        }
        public string UserName
        {
            get
            {
                return username;
            }
            set
            {
                username = value;
            }
        }
        public string DisplayName
        {
            get
            {
                return displayName;
            }
            set
            {
                displayName = value;
            }
        }

        public string Password
        {
            get
            {
                return password;
            }
            set
            {
                password = value;
            }
        }

        public string ServerUrl
        {
            get
            {
                return serverUrl;
            }
            set
            {
                serverUrl = value;
            }
        }
        public string SessionId
        {
            get
            {
                return sessionId;
            }
            set
            {
                sessionId = value;
            }
        } 
        #endregion

        private void resetBindingStub()
        {
            sfs = new SforceService();
            //sfs.Url = host;
            sfs.Timeout = 60000;
            this.assignmentRules = null;
            this.queryOptions = null;
        }
        private void setCompressionOnBinding()
        {
            sfs.EnableDecompression = useCompression;
        }

        /**
         * This method is used to log in to salesforce and set the
         * private class variables for the wrapper, including the
         * session ID.
         */
        public void login()
        {
            resetBindingStub();
            LoginResult loginResult = sfs.login(username, password);
            this.nextLoginTime = DateTime.Now.Ticks +
                                 (this.sessionlength * 6000000);
            this.sessionId = loginResult.sessionId;
            this.serverUrl = loginResult.serverUrl;


            // Create a new session header object and set the
            // session id to that returned by the login
            SessionHeader sh = new SessionHeader();
            sh.sessionId = sessionId;
            sfs.SessionHeaderValue = sh;
            sfs.Url = serverUrl;
            this.displayName = loginResult.userInfo.userFullName;
            if (useMeta)
            {
                mts.SessionHeaderValue =  new Weibo2Sfdc.SfMeta.SessionHeader();
                mts.SessionHeaderValue.sessionId = loginResult.sessionId;
                mts.Url = loginResult.metadataServerUrl;
            }
        }


        private void checkLogin()
        {
            if (this.loginRequired())
            {
                if (String.IsNullOrEmpty(UserName)
                    || String.IsNullOrEmpty(Password))
                {
                    loginBySessionId();
                }
                else
                {
                    login();
                }
            }
        }
        /**
        * This method is used to log in with an existing sessionId
        * @param String sid sessionId
        * @param String sURL serverUrl
        */
        public void loginBySessionId()
        {
            this.nextLoginTime = DateTime.Now.Ticks +
                                 (this.sessionlength * 6000000);
            resetBindingStub();
            SessionHeader sh = new SessionHeader();
            sh.sessionId = sessionId;
            sfs.SessionHeaderValue = sh;
            sfs.Url = serverUrl;
        }

        /**
         * This method checks whether the session is active or not
         * @return boolean
         */

        public bool isConnected()
        {
            return DateTime.Now.Ticks < nextLoginTime;
        }

        /**
          * This method returns true if a login to Salesforce is
          * necessary, otherwise false. It should be used to check the
          * session length before performing any API calls.
          */
        private bool loginRequired()
        {
            if (sessionId == null || sessionId.Length == 0)
                return true;
            return !isConnected();
        }
        private void setBatchSizeHeader(int batchSize)
        {
            if (queryOptions == null)
            {
                this.queryOptions = new QueryOptions();
                sfs.QueryOptionsValue = queryOptions;
            }
            queryOptions.batchSize = batchSize;
        }

        /**
         * This method queries the database and returns the results.
         * @param String strSOQLStmt
         * @return SObject[]
         */
        public QueryResult executeQuery(string strSOQLStmt, int queryBatchSize)
        {
            checkLogin();
            setBatchSizeHeader(queryBatchSize == 0 ? querySize : queryBatchSize);
            return sfs.query(strSOQLStmt);
        }

        public QueryResult executeSOQL(String strSOQLStmt)
        {
            return executeQuery(strSOQLStmt, 0);
        }

        public QueryResult executeQueryMore(String queryLocator)
        {
            checkLogin();
            return sfs.queryMore(queryLocator);
        }

        /**
         * This method creates an array of sObjects with a specified
         * batchSize.
         * @param records
         * @param batchSize
         * @return SaveResult[]
         */
        public SaveResult[] create(sObject[] records, int batchSize)
        {
            if (batchSize > 200 || batchSize < 1)
                throw new ArgumentException(
                              "batchSize must be between 1 and 200");
            return batch(records, batchSize, new CreateBatcher());
        }


        public SaveResult[] create(sObject[] records)
        {
            return create(records, 200);
        }


        public SaveResult[] update(sObject[] records, int batchSize)
        {
            if (batchSize > 200 || batchSize < 1)
                throw new ArgumentException(
                                   "batchSize must be between 1 and 200");
            return batch(records, batchSize, new UpdateBatcher());
        }

        /**
         * This method updates an array of sObjects with a specified
         * batchSize.
         * @param records
         * @param batchSize
         * @return SaveResult[]
         */

        public SaveResult[] update(sObject[] records)
        {
            return update(records, 200);
        }

        private SaveResult[] batch(sObject[] records, int batchSize,
                                   Batcher batchOperation)
        {
            if (records.Length <= batchSize)
            {
                checkLogin();
                return batchOperation.perform(this, records);
            }
            SaveResult[] saveResults = new SaveResult[records.Length];
            sObject[] thisBatch = null;
            int pos = 0;
            while (pos < records.Length)
            {
                int thisBatchSize = Math.Min(batchSize,
                                             records.Length - pos);
                if (thisBatch == null ||
                        thisBatch.Length != thisBatchSize)
                    thisBatch = new sObject[thisBatchSize];

                Array.Copy(records, pos, thisBatch, 0,
                                 thisBatchSize);
                SaveResult[] batchResults = batch(thisBatch,
                                                   thisBatchSize,
                                                   batchOperation);
                Array.Copy(batchResults, 0, saveResults,
                                 pos, thisBatchSize);
                pos += thisBatchSize;
            }
            return saveResults;
        }


        private interface Batcher
        {
            SaveResult[] perform(Client client, sObject[] records);
        }


        private class CreateBatcher : Batcher
        {
            SaveResult[] Batcher.perform(Client client, sObject[] records)
            {
                client.checkLogin();
                return client.sfs.create(records);
            }
        }

        private class UpdateBatcher : Batcher
        {
            SaveResult[] Batcher.perform(Client client, sObject[] records)
            {
                client.checkLogin();
                return client.sfs.update(records);
            }
        }

        public Weibo2Sfdc.SfMeta.FileProperties[] GetMetaInfo()
        {
            Weibo2Sfdc.SfMeta.ListMetadataQuery query = new Weibo2Sfdc.SfMeta.ListMetadataQuery();
            query.type = "Object";
            Weibo2Sfdc.SfMeta.FileProperties[] fps = mts.listMetadata(new Weibo2Sfdc.SfMeta.ListMetadataQuery[] { query }, 21.0);
            return fps;
        }
    }
}